#include "DropletRenderer.h"

#ifdef _LAB
const char *shaderDirectory = "E:\\Documents\\My Dropbox\\Work\\Grad_School\\Lab\\Droplets\\DropletRenderer\\Shaders\\";
const char *modelDirectory = "E:\\Documents\\My Dropbox\\Work\\Grad_School\\Lab\\Droplets\\DropletRenderer\\Models\\";

#else	// _HOME
const char *shaderDirectory = "G:\\Documents\\My Dropbox\\Work\\Grad_School\\Lab\\Droplets\\DropletRenderer\\Shaders\\";
const char *modelDirectory = "G:\\Documents\\My Dropbox\\Work\\Grad_School\\Lab\\Droplets\\DropletRenderer\\Models\\";
#endif

DropletRenderer::DropletRenderer() 
	: OGLRenderer()		// Base class constructor
{
	firstUpdate = true;
	renderableObjects.clear();
	srand((unsigned)time(0));	// Seed the random number generator

	dropletAngle = 0.0f;
}

DropletRenderer::~DropletRenderer() { return; }

void DropletRenderer::LoopInitialize()
{
	// Create and bing a Vertex Array Object
	createVAO();
	glBindVertexArray(vaoID);

	// Create and use a Shader Program
	std::string vertexShaderPath = std::string(shaderDirectory);
	std::string fragmentShaderPath = std::string(shaderDirectory);
	loadProgram(vertexShaderPath.append(VERTEX_SHADER_FILE),
		fragmentShaderPath.append(FRAGMENT_SHADER_FILE));
	
	// Start from the top left tile's bottom left vertex
	float l = TILE_LENGTH;
	float xTilePos = -l * NUM_TILE_COLS / 2.0f;
	float yTilePos = (l * NUM_TILE_ROWS / 2.0f) - l;

	int objectCounter = 0;

	// CREATE FLOOR TILES
	for(int i = 0; i < NUM_TILE_ROWS * NUM_TILE_COLS; i++)
	{
		if(xTilePos >= (l * NUM_TILE_COLS / 2.0f))
		{
			xTilePos = -l * NUM_TILE_COLS / 2.0f;
			yTilePos -= l;
		}
		
		std::string floorModelPath = std::string(modelDirectory);
		RenderableSimpleDroplet *floor = createRenderableObject(
			floorModelPath.append(FLOOR_MODEL_FILE),
			glm::vec3(xTilePos, yTilePos, 0.0f),
			GL_ARRAY_BUFFER,
			GL_STATIC_DRAW,
			GL_TRIANGLE_STRIP);
		floor->geomData->SetScaleMat(glm::vec3(l, l, 0.0f));

		xTilePos += l;
		objectCounter++;
	}

	// CREATE DROPLET OBJECTS
	float floorWidth = l * NUM_TILE_COLS;
	float floorLength = l * NUM_TILE_ROWS;
	float dropletRadius = DROPLET_DIAMETER / 2.0f;
	float posRangeWidth = floorWidth / 2.0f;
	float posRangeLength = floorLength / 2.0f;
	for(int i = 0; i < NUM_ROBOTS; i++)
	{	
		std::string dropletModelPath = std::string(modelDirectory);
		RenderableSimpleDroplet *droplet = createRenderableObject(
			dropletModelPath.append(DROPLET_MODEL_FILE),
			glm::vec3(
				getRandomf(-posRangeWidth + dropletRadius, posRangeWidth - dropletRadius),
				getRandomf(-posRangeLength + dropletRadius, posRangeLength - dropletRadius), 
				0.0f
			),
			GL_ARRAY_BUFFER,
			GL_STATIC_DRAW,
			GL_TRIANGLE_FAN
		);
		droplet->geomData->SetScaleMat(glm::vec3(
			DROPLET_DIAMETER / 2.0f, 
			DROPLET_DIAMETER / 2.0f,
			1.0f)
		);
		objectCounter++;
	}
	
	printf("[DEBUG] Total Objects initialized = %i\n", objectCounter);

	glUseProgram(programID);

	printf("[DEBUG] Total Drawable objects created = %i\n", renderableObjects.size());
}

void DropletRenderer::LoopIterate()
{		
	// Clear Buffers
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	
	// Set the camera and schene params
	glm::mat4 pvMat = viewControl->GetPVMat();
	GLuint pvMatID = glGetUniformLocation(programID, "PV");
	glUniformMatrix4fv(pvMatID, 1, GL_FALSE, &pvMat[0][0]);

	// Render the models
	std::vector<RenderableSimpleDroplet *>::iterator it;
	for(it = renderableObjects.begin();
		it != renderableObjects.end();
		it++)
	{
		RenderableSimpleDroplet *rObj = *it;
		rObj->Render(vaoID, programID);
	}

}

void DropletRenderer::LoopUpdate()
{
	// DEBUG FIX : This 'if' code-block will only run once. Nothing gets rendered otherwise.
	if(firstUpdate)
	{
		firstUpdate = false;
		viewControl->FirstUpdate();
	}

	// TODO : THE FOLLOWING CODE BLOCK WILL EVENTUALLY BE HANDLED BY THE DROPLET SIMULATOR
	// For now just move the droplets randomly
	
	// Loop over the droplets and rotate them...
	dropletAngle += DELTA_ANGLE;
	if(dropletAngle >= ((float)M_PI * 2.0f))
		dropletAngle -= ((float)M_PI * 2.0f);
	for(int i = NUM_TILE_COLS * NUM_TILE_ROWS; i < renderableObjects.size(); i++)
	{
		renderableObjects[i]->geomData->SetRotationMat(
			dropletAngle,
			glm::vec3(0.0f, 0.0f, 1.0f));

		renderableObjects[i]->geomData->SetTranslationMatRelative(
			glm::vec3(getRandomf(-2.0f, 1.0f), getRandomf(-2.0f, 1.0f), 0.0f));
	}

	// TODO : Put these handlers in a timer later
	// Scan for user input
	viewControl->KeyboardActionHandler();
	viewControl->MouseActionHandler();

	// Swap render buffers
	glfwSwapBuffers();
}

void DropletRenderer::LoopComplete()
{
	// Destroy Vertex Bufffer Objects
	OGLResult retval = OGL_SUCCESS;

	std::vector<RenderableSimpleDroplet *>::iterator it;
	for(it = renderableObjects.begin();
		it != renderableObjects.end();
		it++)
	{
		RenderableSimpleDroplet *rObj = *it;
		retval = rObj->Cleanup();
		if(retval != OGL_SUCCESS)
		{
			fprintf(stderr, "[ERROR] Could not delete Vertex Buffer Object with index %u",
				rObj->objData->GetVBOID());
		}

		delete rObj;
	}
	renderableObjects.clear();

	// Destroy Shaders
	retval = destroyShadersAndPrograms();
	if(retval != OGL_SUCCESS)
	{
		fprintf(stderr, "[ERROR] Could not delete Program with index %u", programID);
	}

	// Destroy Vertex Array Objects
	glBindVertexArray(0);
	glDeleteVertexArrays(1, &vaoID);
}

OGLResult DropletRenderer::createVAO()
{
	GLenum errorCheckValue = glGetError();
	glGenVertexArrays(1, &vaoID);
	glBindVertexArray(vaoID);

	errorCheckValue = glGetError();
	if (errorCheckValue != GL_NO_ERROR)
	{
		fprintf(stderr, "[ERROR] Could not create a VAO: %s \n",
			gluErrorString(errorCheckValue));
		return OGL_ERROR;
	}

	return OGL_SUCCESS;
}

// Taken from : http://www.opengl-tutorial.org/beginners-tutorials/tutorial-2-the-first-triangle/
OGLResult DropletRenderer::loadShader(
	std::string shaderFilepath, 
	GLenum shaderType,
	GLuint *shaderID_out)
{
    // Create the shaders
	GLuint shaderID;
    shaderID = glCreateShader(shaderType);
 
    // Read the Shader code from the file
    std::string shaderCode;
    std::ifstream shaderStream(shaderFilepath, std::ios::in);
    if(shaderStream.is_open())
    {
        std::string line = "";
        while(std::getline(shaderStream, line))
            shaderCode += "\n" + line;
        shaderStream.close();
    }
 
    GLint result = GL_FALSE;
    int infoLogLength;
 
    // Compile Shader
	printf("Compiling shader : %s\n", shaderFilepath.c_str());
    char const *sourcePointer = shaderCode.c_str();
    glShaderSource(shaderID, 1, &sourcePointer , NULL);
    glCompileShader(shaderID);
 
    // Check Shader
    glGetShaderiv(shaderID, GL_COMPILE_STATUS, &result);
    glGetShaderiv(shaderID, GL_INFO_LOG_LENGTH, &infoLogLength);
    std::vector<char> shaderErrorMessage(infoLogLength);
    glGetShaderInfoLog(shaderID, infoLogLength, NULL, &shaderErrorMessage[0]);

	if(result == GL_FALSE)
		fprintf(stderr, "[FATAL] Shader Compile operation failed.\n");

    fprintf(stdout, "%s\n", &shaderErrorMessage[0]);
 
	*shaderID_out = shaderID;

	return OGL_SUCCESS;
}

// Taken from : http://www.opengl-tutorial.org/beginners-tutorials/tutorial-2-the-first-triangle/
// The program is created AND LINKED in this function. To use another program immediately
// after calling this function, unlink this one first.
OGLResult DropletRenderer::loadProgram(
	std::string vertexShaderFilepath, 
	std::string fragmentShaderFilepath)
{
	// Load the shaders
	loadShader(vertexShaderFilepath, GL_VERTEX_SHADER, &vertexShaderID);
	loadShader(fragmentShaderFilepath, GL_FRAGMENT_SHADER, &fragmentShaderID);

	// Link the program
    fprintf(stdout, "Linking program\n");
    programID = glCreateProgram();
	
	// Attach the shaders
	glAttachShader(programID, vertexShaderID);
	glAttachShader(programID, fragmentShaderID);
    glLinkProgram(programID);
 
    // Check the program
	GLint result = GL_FALSE;
	GLint uniforms = -1;
    int infoLogLength;
    glGetProgramiv(programID, GL_LINK_STATUS, &result);
	glGetProgramiv(programID, GL_ACTIVE_UNIFORMS, &uniforms);
    glGetProgramiv(programID, GL_INFO_LOG_LENGTH, &infoLogLength);
    std::vector<char> programErrorMessage( std::max(infoLogLength, int(1)) );
    glGetProgramInfoLog(programID, infoLogLength, NULL, &programErrorMessage[0]);
	
	if(result == GL_FALSE)
		fprintf(stderr, "[FATAL] Shader Compile operation failed.\n");

	printf("[DEBUG] Active Uniforms in programID %u = %i\n", programID, uniforms);
    printf("[DEBUG] Program Message(s) : %s\n", &programErrorMessage[0]);
 
    return OGL_SUCCESS;
}

OGLResult DropletRenderer::destroyShadersAndPrograms()
{
	GLenum errorValue = glGetError();
	glUseProgram(0);

	GLsizei count = 0;
	GLuint *shaderIDs = (GLuint *)malloc(sizeof(GLuint) * MAX_SHADERS_PER_PROGRAM);
	glGetAttachedShaders(programID, MAX_SHADERS_PER_PROGRAM, &count, shaderIDs);
	for(GLsizei i = 0; i < count; i++)
		glDetachShader(programID, shaderIDs[i]); 
	for(GLsizei i = 0; i < count; i++)
		glDeleteShader(shaderIDs[i]); 
		
	glDeleteProgram(programID);
	free(shaderIDs);

	errorValue = glGetError();
	if (errorValue != GL_NO_ERROR)
	{
		fprintf(
			stderr,
			"[ERROR] Could not destroy the shaders: %s \n",
			gluErrorString(errorValue)
		);

		return OGL_ERROR;
	}

	return OGL_SUCCESS;
}

RenderableSimpleDroplet *DropletRenderer::createRenderableObject(
	std::string modelFilepath,
	glm::vec3 startPosition,
	GLenum dataType,
	GLenum usageType,
	GLenum drawType)
{
	RenderableSimpleDroplet *rObj = new RenderableSimpleDroplet(
		new OGLDataObject(),
		new OGLGeometryData(startPosition)	// Set starting position
	);
	rObj->objData->dataType = dataType;
	rObj->objData->usageType = usageType;
	rObj->objData->vaoID = vaoID;
	rObj->objData->programID = programID;
	rObj->geomData->drawType = drawType;

	// Load the model
	if(rObj->LoadModel(modelFilepath) != OGL_SUCCESS)
	{
		fprintf(stderr, "[WARNING] rObj Model initialization failed.\n");
	}
//	rObj->DEBUG_PrintVertexData();
	renderableObjects.push_back(rObj);

	return rObj;
}

float DropletRenderer::getRandomf(float min, float max)
{
	float range=(max - min) + 1.0f;
	return min + float(range*rand()/(RAND_MAX + 1.0f));
}